//-------------------------------------------------------------------
//	Copyright (c) 2013  Zhirnov Andrey
//	This file is part of the "Noise Library" project.
//	See copyright notice in "linNoiseCL.h".
//-------------------------------------------------------------------

#pragma once

#include "../CSource.h"

namespace Noise
{

	//
	// Function Source for coordinate displacement.
	//

	class CSourceDisplace : public CSource
	{
	// types
	protected:
		typedef CSourceDisplace		Self;


	// variables
	protected:
		CSourcePtr	_src0,
					_src1,
					_src2,
					_src3;


	// methods
	public:
		CSourceDisplace ()
		{}

		~CSourceDisplace ()
		{}

		ESourceState GetState () const
		{
			return (_src0.IsNotNull() and _src1.IsNotNull() and _src2.IsNotNull() and _src3.IsNotNull()) ?
					SOURCE_COMPLETE : UNCOMPLETE_ATTACHMENT_SOURCE;
		}
		
		virtual void Delete ()
		{
			_src0 = null;
			_src1 = null;
			_src2 = null;
			_src3 = null;
		}

		virtual EResult GetInteger (ESourceQuery type, int *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case SOURCE_STATE :
					v[0] = GetState();
					return OK;

				case SOURCE_TYPE :
					v[0] = DISPLACE;
					return OK;

				case SOURCES_COUNT :
				{
					int	count0 = 0,
						count1 = 0,
						count2 = 0,
						count3 = 0;

					_src0->GetInteger( SOURCES_COUNT, &count0, 1 );
					_src1->GetInteger( SOURCES_COUNT, &count1, 1 );
					_src2->GetInteger( SOURCES_COUNT, &count2, 1 );
					_src3->GetInteger( SOURCES_COUNT, &count3, 1 );

					v[0] = count0 + count1 + count2 + count3 + 1;
					return OK;
				}

				case INPUT_SOURCES :
					v[0] = 4;
					return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult SetArg (EArgument type, CSourcePtr src)
		{
			switch ( type )
			{
				case SOURCE_0 :	_src0 = src;	return OK;
				case SOURCE_1 :	_src1 = src;	return OK;
				case SOURCE_2 :	_src2 = src;	return OK;
				case SOURCE_3 : _src3 = src;	return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult GetArg (EArgument type, CSourcePtr &src) const
		{
			switch ( type )
			{
				case SOURCE_0 :	src = _src0;	return OK;
				case SOURCE_1 :	src = _src1;	return OK;
				case SOURCE_2 :	src = _src2;	return OK;
				case SOURCE_3 : src = _src3;	return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual CSourcePtr Clone () const override
		{
			return new Self();
		}
		
		virtual void PreBuild (const TPreBuildData &data) override
		{
			data.Push( _src0.ptr() );
			data.Push( _src1.ptr() );
			data.Push( _src2.ptr() );
			data.Push( _src3.ptr() );
		}

		virtual bool BuildFunction (TFunctionData &data) override
		{
			CSource::BuildFunctionHeader( data.source, _id );
			data.source
				<< " ( " << CSource::FunctionName() << _src0->GetSourceId() << "( "
				<< "vPoint + (real3)( "
				<< CSource::FunctionName() << _src1->GetSourceId() << CSource::FunctionArgs() << ", "
				<< CSource::FunctionName() << _src2->GetSourceId() << CSource::FunctionArgs() << ", "
				<< CSource::FunctionName() << _src3->GetSourceId() << CSource::FunctionArgs()
				<< " ) ) )\n";
			return true;
		}
		
		NATIVE(
		template < typename Real, Real (CSource::*NativeFunction)(const TVec<Real,3> &) >
		Real NativeFunction (const TVec<Real,3> &vPoint)
		{
			typedef TVec<Real,3>	Real3;

			Real3 displace = Real3( (_src1.ptr()->*NativeFunction)( vPoint ),
									(_src2.ptr()->*NativeFunction)( vPoint ),
									(_src3.ptr()->*NativeFunction)( vPoint ) )
							+ vPoint;
			return (_src0.ptr()->*NativeFunction)( displace );
		}
		
		virtual float32 NativeFunction32 (const fvec3 &vPoint) override
		{
			return NativeFunction< float32, &CSource::NativeFunction32 >( vPoint );
		}

		virtual float64 NativeFunction64 (const dvec3 &vPoint) override
		{
			return NativeFunction< float64, &CSource::NativeFunction64 >( vPoint );
		})

		static bool CreateSource (CSourcePtr &src, CContext *context, ESource type)
		{
			ASSERT( type == DISPLACE );

			src = new Self();
			return true;
		}
	};

}	// Noise